#include <ostream>
#include <map>

#include "coapconstants.h"
#include "coapclienttask.h"
#include "options.h"

using namespace std;

/*
 0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |Ver| T |  OC   |      Code     |          Message ID           |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |   Options (if any) ...
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |   Payload (if any) ...
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/

///////////////////////////////////////////////
// Private methods
//////////////////////////////////////////////

Uint16 CoapClientTask::getMsgID(void) const
{
   return msgID_;
}

void CoapClientTask::setMsgID(void)
{
   //TODO - what about better type of generation?
   //TODO - The same Message ID MUST NOT be re-used within the potential retransmission window, calculated as
   // RESPONSE_TIMEOUT * (2 ^ MAX_RETRANSMIT - 1) plus the expected maximum round trip time.
   msgID_++;
}


Uint8 CoapClientTask::getToken(void)
{
   return token_++;
}

Options& CoapClientTask::getOptions(void)
{
   return opt;
} 


///////////////////////////////////////////////
// Public methods
//////////////////////////////////////////////

// Constructors 
CoapClientTask::CoapClientTask(std::string name, Scheduler* s, CoapClientTaskSM* sm,
		       InetAddr serveraddr)
  : PTask(name, s, sm), 
    up(this),           
    peer(this, &down),  
    down(this, &peer), 
    token_(0x44),
    msgID_(0x0044),
    retries_(0),
    serveraddr_(serveraddr),
    timer_(this, OTime(RESPONSE_TIMEOUT)),
    pdu_(NULL),
    opt()
{
  // These are usually SHOULD
  STATIC_SYMBOL(up, this);
  STATIC_SYMBOL(down, this);
  STATIC_SYMBOL(peer, this);
  // These are optional but give additional information to debugger
  STATIC_SYMBOL(timer_, this);
}

// Destructor
CoapClientTask::~CoapClientTask()
{
  // Delete symbols from the hierarchy
  DELETE_SYMBOL(&up);
  DELETE_SYMBOL(&down);
  DELETE_SYMBOL(&peer);
  DELETE_SYMBOL(&timer_);
}

//////////////
// Idle state
//////////////
bool CoapClientTask::idle_Cgetreq(Message* msg)
{
   // Get primitive
   CoapClientSAP::Cgetreq *cGetReq = 
      (CoapClientSAP::Cgetreq *)msg;

   // TODO: check if URI is valid
   // if not return error primitive 
   // to the user
   
   // Create PDU
   CoapPeer::CON* pdu = (CoapPeer::CON *) peer.create(CoapPeer::con);
   if (!pdu) {
      return true;
   }
   msgID_ = getMsgID();

   //////////////////
   // Header 4 bytes
   //////////////////
   // First byte
   Uint8 temp = HEADER_COAP_VERSION 
      | HEADER_TYPE_CON
      | HEADER_OC_CON;
   pdu->header0 = temp;
   // Second byte
   pdu->code = METHOD_GET;
   // Third & fourth byte
   pdu->msgID = msgID_;

    //////////////////
   // Options
   //////////////////

   // Uri-Path
   if(!(cGetReq->uripath.empty()))
   {
      //delta 0x9
      // Option header
      if(cGetReq->uripath.length() < 15) // Option length on 4 bits
      {
	 Uint8 option= OPT_URI_PATH
	    | (cGetReq->uripath.length()) << 4;
	 pdu->options.putLast(option);
      }
      else // Option length on 12 bits
      {
	 pdu->options.putLast(OPT_URI_PATH << 4 | 0x0f);
	 pdu->options.putLast(cGetReq->uripath.length() - 15);
      }
      // Option value
      pdu->options.putLast((const Byte *)cGetReq->uripath.data(), 
			   cGetReq->uripath.length());
   }
   
   // Token
   // Option header
   Uint8 option= (OPT_TOKEN - OPT_URI_PATH) // delta
      | (TOKEN_SIZE << 4);
   pdu->options.putLast(option);
   // Option value
   option = getToken();
   pdu->options.putLast(option);
   
   //////////////////
   // Payload
   //////////////////
   // No payload in GET method
   
   // Send PDU
   pdu_= (CoapPeer::CON*) pdu->clone();
   sendMsg(pdu);
   
   // Move to wait state
   state = CoapClientTaskSM::Wait;
  
   return false;
}

bool CoapClientTask::idle_Cpostreq(Message* msg)
{   
   return false;
}

bool CoapClientTask::idle_Cputreq(Message* msg)
{
   // Get primitive
   CoapClientSAP::Cputreq *cPutReq = (CoapClientSAP::Cputreq*) msg;

   // TODO: check if URI is valid
   // if not return error primitive 
   // to the user

   // Create PDU
   CoapPeer::CON* pdu = (CoapPeer::CON *) peer.create(CoapPeer::con);
   if (!pdu) {
      return true;
   }
   msgID_ = getMsgID();

   //////////////////
   // Header 4 bytes
   //////////////////
   // First byte
   Uint8 temp = HEADER_COAP_VERSION 
      | HEADER_TYPE_CON
      | HEADER_OC_CON;
   pdu->header0 = temp;
   // Second byte
   pdu->code = METHOD_PUT;
   // Third & fourth byte
   pdu->msgID = msgID_;

    //////////////////
   // Options
   //////////////////

	// Uri-Path
	if(!(cPutReq->uripath.empty()))
	{
		//delta 0x9
		// Option header
		if(cPutReq->uripath.length() < 15) // Option length on 4 bits
		{
			Uint8 option= OPT_URI_PATH  | (cPutReq->uripath.length()) << 4;
			pdu->options.putLast(option);
		}
		else // Option length on 12 bits
		{
			pdu->options.putLast(OPT_URI_PATH << 4 | 0x0f);
			pdu->options.putLast(cPutReq->uripath.length() - 15);
		}
    	// Option value
		pdu->options.putLast((const Byte *)cPutReq->uripath.data(), cPutReq->uripath.length());
	}

   // Token
   // Option header
   Uint8 option= (OPT_TOKEN - OPT_URI_PATH) // delta
      | (TOKEN_SIZE << 4);
   pdu->options.putLast(option);
   // Option value
   option = getToken();
   pdu->options.putLast(option);

   //////////////////
   // Payload
   //////////////////
   pdu->payload.combine(cPutReq->payload);
     
   // Send PDU
   pdu_= (CoapPeer::CON*) pdu->clone();

   sendMsg(pdu);
     
   // Move to wait state
   state = CoapClientTaskSM::Wait;

   return false;
}

bool CoapClientTask::idle_Cdeletereq(Message* msg)
{
   // Get primitive
   CoapClientSAP::Cdeletereq *cDelReq = 
      (CoapClientSAP::Cdeletereq *)msg;

   // TODO: check if URI is valid
   // if not return error primitive 
   // to the user
   
   // Create PDU
   CoapPeer::CON* pdu = (CoapPeer::CON *) peer.create(CoapPeer::con);
   if (!pdu) {
      return true;
   }
   msgID_ = getMsgID();

   //////////////////
   // Header 4 bytes
   //////////////////
   // First byte
   Uint8 temp = HEADER_COAP_VERSION 
      | HEADER_TYPE_CON
      | HEADER_OC_CON;
   pdu->header0 = temp;
   // Second byte
   pdu->code = METHOD_DELETE;
   // Third & fourth byte
   pdu->msgID = msgID_;

    //////////////////
   // Options
   //////////////////

   // Uri-Path
   if(!(cDelReq->uripath.empty()))
   {
      //delta 0x9
      // Option header
      if(cDelReq->uripath.length() < 15) // Option length on 4 bits
      {
	 Uint8 option= OPT_URI_PATH
	    | (cDelReq->uripath.length()) << 4;
	 pdu->options.putLast(option);
      }
      else // Option length on 12 bits
      {
	 pdu->options.putLast(OPT_URI_PATH << 4 | 0x0f);
	 pdu->options.putLast(cDelReq->uripath.length() - 15);
      }
      // Option value
      pdu->options.putLast((const Byte *)cDelReq->uripath.data(), 
			   cDelReq->uripath.length());
   }
   
   // Token
   // Option header
   Uint8 option= (OPT_TOKEN - OPT_URI_PATH) // delta
      | (TOKEN_SIZE << 4);
   pdu->options.putLast(option);
   // Option value
   option = getToken();
   pdu->options.putLast(option);
   
   //////////////////
   // Payload
   //////////////////
   // No payload in DEL method
   
   // Send PDU
   pdu_= (CoapPeer::CON*) pdu->clone();
   sendMsg(pdu);
   
   // Move to wait state
   state = CoapClientTaskSM::Wait;

   return false;
}

//////////////
// Wait state
//////////////
bool CoapClientTask::wait_ACK(Message* msg)
{
	std::cout << "**************************** INCOMMING ACK ******************************" << std::endl;
	// Get PDU
	CoapPeer::ACK *pdu = (CoapPeer::ACK *)msg;

	// Create primitive
	CoapClientSAP::Ackind* primitive = (CoapClientSAP::Ackind *)up.create(CoapClientSAP::ackind);

	// Do only when message ID and Token matches  
	getOptions().parseOptions(pdu->options, (pdu->header0 & 0xf0) >> 4);   
	std::string token= getOptions().getOption(OPT_TOKEN);
	std::cout << "*********************************************************" << token[0] << " vs " << token_ << std::endl;
   	if (serveraddr_ == pdu->addr  && msgID_ == pdu->msgID && token[0] == token_-1){
   
		// Read data from PDU
		primitive->responseCode = pdu->code;
		primitive->payload = pdu->payload;

		// Send primitive to user
		up.putMessage(primitive);
      
		// Move to idle state
		state = CoapClientTaskSM::Idle;
   }
   
   return false;
}

bool CoapClientTask::wait_RST(Message* msg)
{
   // Get PDU
   CoapPeer::RST *pdu = (CoapPeer::RST *)msg;

   // Create primitive
   CoapClientSAP::Resetind* primitive = 
      (CoapClientSAP::Resetind *)up.create(CoapClientSAP::resetind);
   
   // Read error code
   if (serveraddr_ == pdu->addr 
       && msgID_ == pdu->msgID) {

      // Read data from PDU
      primitive->responseCode = pdu->code;

      // Send primitive to user
      up.putMessage(primitive);
      
      // Move to idle state
      state = CoapClientTaskSM::Idle;
   }

   return false;
}

bool CoapClientTask::wait_Timeout(Message* msg)
{  
   std::cout << "Timeout" << std::endl;
   if (retries_ > MAX_RETRANSMIT) {
      OTime timeVar(RESPONSE_TIMEOUT);
      timer_.stop();
      timer_.setTimeout(timeVar);
      retries_ = 0;
      state = CoapClientTaskSM::Idle;
   }
   else {
      OTime timeVar(++retries_ * RESPONSE_TIMEOUT * 2);
      timer_.setTimeout(timeVar);
      std::cout << "******************************************************** RETRANSMISSION" << std::endl;
      CoapPeer::CON* pdu= (CoapPeer::CON*) pdu_->clone();
      sendMsg(pdu);
   }   
   return false;
}
